#!/usr/bin/python
# -*- coding: utf-8 -*-
# @Version        : 1.2
# @Update Time    : 2025/8/26 11:00
# @File           : file_factory.py
# @IDE            : PyCharm
# @Desc           : 增强版文件上传工厂类，用于创建和管理不同的文件上传策略，支持多种文件类型和分片上传

from typing import Dict, Optional, Type, List, Any
from fastapi import UploadFile

from applications import settings
from applications.core.exceptions import BaseAppException
from applications.utils.singleton import Singleton
from applications.utils.upload_file.base import AbstractUpload, UploadResult
from applications.utils.upload_file.strategy.local_file import LocalFileStrategy
from applications.utils.upload_file.strategy.qiniu_file import QiniuFileStrategy


class FileUploadFactory(metaclass=Singleton):
    """增强版文件上传工厂类，用于创建和管理不同的文件上传策略

    使用单例模式确保全局只有一个工厂实例，避免重复创建上传策略对象。
    支持本地文件上传和七牛云文件上传，提供统一的文件上传、删除和查询接口。
    新增支持批量上传、详细结果返回、缩略图生成等功能。

    Attributes:
        _upload_strategy: 存储已创建的上传策略实例
        _default_upload_type: 默认上传类型
    """

    _upload_strategy: Dict[str, AbstractUpload] = {}
    _default_upload_type: str = None

    @classmethod
    def set_default_upload_type(cls, upload_type: str) -> None:
        """设置默认上传类型

        Args:
            upload_type: 上传类型
        """
        cls._default_upload_type = upload_type

    @classmethod
    def get_default_upload_type(cls) -> str:
        """获取默认上传类型

        Returns:
            str: 默认上传类型
        """
        if cls._default_upload_type:
            return cls._default_upload_type
        return getattr(settings, "DEFAULT_UPLOAD_TYPE", "local")

    @classmethod
    def get_available_strategies(cls) -> List[str]:
        """获取可用的上传策略列表

        Returns:
            List[str]: 可用的上传策略列表
        """
        return ["local", "qiniu"]

    @classmethod
    def get_upload_strategy(cls, upload_type: str = None) -> AbstractUpload:
        """获取上传策略实例

        Args:
            upload_type: 上传类型，支持 "local" 和 "qiniu"，默认为配置文件中的设置

        Returns:
            AbstractUpload: 上传策略实例

        Raises:
            BaseAppException: 不支持的上传类型时抛出
        """
        # 如果未指定上传类型，则使用默认值
        if not upload_type:
            upload_type = cls.get_default_upload_type()

        if upload_type not in cls._upload_strategy:
            strategy = cls._create_strategy(upload_type)
            if strategy:
                cls._upload_strategy[upload_type] = strategy
            else:
                raise BaseAppException(msg=f"暂不支持{upload_type}上传")
        return cls._upload_strategy[upload_type]

    @classmethod
    def _create_strategy(cls, upload_type: str) -> Optional[AbstractUpload]:
        """创建上传策略实例

        Args:
            upload_type: 上传类型

        Returns:
            Optional[AbstractUpload]: 上传策略实例，如果类型不支持则返回None
        """
        strategy_map: Dict[str, Type[AbstractUpload]] = {
            "local": LocalFileStrategy,
            "qiniu": QiniuFileStrategy,
        }
        strategy_class = strategy_map.get(upload_type)
        return strategy_class() if strategy_class else None

    @classmethod
    async def upload_with_result(
        cls,
        path: str,
        file: UploadFile,
        upload_type: str = None,
        file_type: str = "file",
        accept: Optional[List[str]] = None,
        max_size: int = 5,
        create_thumbnail: bool = False,
    ) -> UploadResult:
        """上传文件并返回详细结果

        Args:
            path: 保存路径
            file: 上传的文件
            upload_type: 上传类型
            file_type: 文件类型，可选值为 'file', 'image', 'audio', 'video'
            accept: 允许的文件类型列表
            max_size: 最大文件大小(MB)
            create_thumbnail: 是否创建缩略图（仅对图片有效）

        Returns:
            UploadResult: 上传结果对象

        Raises:
            BaseAppException: 不支持的文件类型或上传失败
        """
        uploader = cls.get_upload_strategy(upload_type)

        return await uploader.upload_with_result(
            path=path,
            file=file,
            accept=accept,
            max_size=max_size,
            create_thumbnail=create_thumbnail and file_type == "image",
        )

    @classmethod
    async def batch_upload(
        cls,
        path: str,
        files: List[UploadFile],
        upload_type: str = None,
        file_type: str = "file",
        accept: Optional[List[str]] = None,
        max_size: int = 5,
    ) -> List[UploadResult]:
        """批量上传文件

        Args:
            path: 保存路径
            files: 上传的文件列表
            upload_type: 上传类型
            file_type: 文件类型
            accept: 允许的文件类型列表
            max_size: 最大文件大小(MB)

        Returns:
            List[UploadResult]: 上传结果列表

        Raises:
            BaseAppException: 不支持的上传类型或上传失败
        """
        uploader = cls.get_upload_strategy(upload_type)

        return await uploader.batch_upload(path=path, files=files, accept=accept, max_size=max_size)

    @classmethod
    async def upload_file(
        cls,
        path: str,
        file: UploadFile,
        upload_type: str = None,
        file_type: str = "file",
        accept: Optional[List[str]] = None,
        max_size: int = 5,
    ) -> str:
        """上传文件（保持向后兼容）

        Args:
            path: 保存路径
            file: 上传的文件
            upload_type: 上传类型
            file_type: 文件类型，可选值为 'file', 'image', 'audio', 'video'
            accept: 允许的文件类型列表
            max_size: 最大文件大小(MB)

        Returns:
            str: 文件访问URL

        Raises:
            BaseAppException: 不支持的文件类型或上传失败
        """
        uploader = cls.get_upload_strategy(upload_type)

        if file_type == "file":
            return await uploader.upload_file(path, file, accept, max_size)
        elif file_type == "image":
            return await uploader.upload_image(path, file, accept, max_size)
        elif file_type == "audio":
            return await uploader.upload_audio(path, file, accept, max_size)
        elif file_type == "video":
            return await uploader.upload_video(path, file, accept, max_size)
        else:
            raise BaseAppException(msg=f"不支持的文件类型: {file_type}")

    @classmethod
    async def upload_large_file(
        cls,
        path: str,
        file: UploadFile,
        chunk_index: int,
        total_chunks: int,
        upload_id: str = None,
        upload_type: str = None,
        accept: Optional[List[str]] = None,
        max_size: int = 100,
    ) -> dict:
        """分片上传大文件

        Args:
            path: 保存路径
            file: 上传的文件分片
            chunk_index: 当前分片索引
            total_chunks: 总分片数
            upload_id: 上传ID，用于标识同一个文件的分片上传
            upload_type: 上传类型
            accept: 允许的文件类型列表
            max_size: 最大文件大小(MB)

        Returns:
            dict: 上传结果信息

        Raises:
            BaseAppException: 不支持的上传类型或上传失败
        """
        uploader = cls.get_upload_strategy(upload_type)

        # 检查上传策略是否支持分片上传
        if not hasattr(uploader, "upload_large_file"):
            raise BaseAppException(msg=f"上传类型 '{upload_type}' 不支持分片上传")

        return await uploader.upload_large_file(
            path=path,
            file=file,
            chunk_index=chunk_index,
            total_chunks=total_chunks,
            upload_id=upload_id,
            accept=accept,
            max_size=max_size,
        )

    @classmethod
    async def delete_file(cls, file_path: str, upload_type: str = None) -> bool:
        """删除文件

        Args:
            file_path: 文件路径
            upload_type: 上传类型

        Returns:
            bool: 删除结果

        Raises:
            BaseAppException: 不支持的上传类型或删除失败
        """
        uploader = cls.get_upload_strategy(upload_type)

        if not hasattr(uploader, "delete_file"):
            raise BaseAppException(msg=f"上传类型 '{upload_type}' 不支持文件删除")

        return await uploader.delete_file(file_path)

    @classmethod
    async def get_file_info(cls, file_path: str, upload_type: str = None) -> dict:
        """获取文件信息

        Args:
            file_path: 文件路径
            upload_type: 上传类型

        Returns:
            dict: 文件信息

        Raises:
            BaseAppException: 不支持的上传类型或获取失败
        """
        uploader = cls.get_upload_strategy(upload_type)

        if not hasattr(uploader, "get_file_info"):
            raise BaseAppException(msg=f"上传类型 '{upload_type}' 不支持获取文件信息")

        return await uploader.get_file_info(file_path)

    @classmethod
    async def get_file_url(cls, file_path: str, upload_type: str = None) -> str:
        """获取文件访问URL

        Args:
            file_path: 文件路径
            upload_type: 上传类型

        Returns:
            str: 文件访问URL

        Raises:
            BaseAppException: 不支持的上传类型
        """
        uploader = cls.get_upload_strategy(upload_type)
        return await uploader.get_file_url(file_path)

    @classmethod
    async def file_exists(cls, file_path: str, upload_type: str = None) -> bool:
        """检查文件是否存在

        Args:
            file_path: 文件路径
            upload_type: 上传类型

        Returns:
            bool: 文件是否存在

        Raises:
            BaseAppException: 不支持的上传类型
        """
        uploader = cls.get_upload_strategy(upload_type)
        return await uploader.file_exists(file_path)

    @classmethod
    def validate_upload_type(cls, upload_type: str) -> bool:
        """验证上传类型是否支持

        Args:
            upload_type: 上传类型

        Returns:
            bool: 是否支持
        """
        return upload_type in cls.get_available_strategies()

    @classmethod
    def get_strategy_info(cls, upload_type: str = None) -> Dict[str, Any]:
        """获取上传策略信息

        Args:
            upload_type: 上传类型

        Returns:
            Dict[str, Any]: 策略信息
        """
        uploader = cls.get_upload_strategy(upload_type)
        return {
            "strategy_type": uploader.get_storage_strategy(),
            "max_file_size": uploader.DEFAULT_MAX_SIZE,
            "large_file_max_size": uploader.LARGE_FILE_MAX_SIZE,
            "chunk_size": uploader.CHUNK_SIZE,
            "supported_types": {
                "image": uploader.IMAGE_ACCEPT,
                "video": uploader.VIDEO_ACCEPT,
                "audio": uploader.AUDIO_ACCEPT,
                "file": uploader.FILE_ACCEPT,
            },
        }
